package com.nervenets.utils;

import com.alibaba.fastjson.JSON;
import org.apache.commons.lang.math.JVMRandom;

import javax.validation.constraints.NotNull;
import java.util.*;

public class RedPacketAlgorithmUtils {

    public static void main(String[] args) {
        int total = 2000000;//红包总金额
        int count = 100000;//红包个数
        int max = total / count * 2 + 1;
        int min = 10;

        List<Long> presets = new ArrayList<>();
        presets.add(2000L);
        presets.add(2000L);
        presets.add(6000L);
        final long start = System.currentTimeMillis();
        System.out.println(start);
        final List<Long> result = presets(total, count, max, min, presets);
        System.out.println(System.currentTimeMillis()-start);
        System.out.println(result.size());
        System.out.println(JSON.toJSONString(result));
        /*long totalMoney = 0;
        for (Long l : result) {
            totalMoney += l;
        }
        System.out.println(totalMoney);*/
    }

    /**
     * 生产min和max之间的随机数，但是概率不是平均的，从min到max方向概率逐渐加大。
     * 先平方，然后产生一个平方值范围内的随机数，再开方，这样就产生了一种“膨胀”再“收缩”的效果。
     *
     * @param min
     * @param max
     * @return
     */
    private static long xRandom(long min, long max) {
        return sqrt(nextLong(sqr(max - min)));
    }

    /**
     * @param money   红包总额
     * @param total   红包个数
     * @param max     每个小红包的最大额
     * @param min     每个小红包的最小额
     * @param presets 预置红包个数及大小
     * @return 存放生成的每个小红包的值的数组
     */
    public static long[] generate(long money, int total, long max, long min, List<Long> presets) {
        long[] result = new long[total];

        long average = money / total + 1;

        //预置部分红包大小
        int i = 0;
        if (null != presets) {
            for (long preset : presets) {
                result[i] = preset;
                money -= preset;
                i++;
            }
        }
        //
        //这样的随机数的概率实际改变了，产生大数的可能性要比产生小数的概率要小。
        //这样就实现了大部分红包的值在平均数附近。大红包和小红包比较少。
        //long range1 = sqr(average - min);
        //long range2 = sqr(max - average);

        for (; i < result.length; i++) {
            //因为小红包的数量通常是要比大红包的数量要多的，因为这里的概率要调换过来。
            //当随机数>平均值，则产生小红包
            //当随机数<平均值，则产生大红包
            if (nextLong(min, max) > average) {
                // 在平均线上减钱
                long temp = min + xRandom(min, average);
                result[i] = temp;
                money -= temp;
            } else {
                // 在平均线上加钱
                long temp = max - xRandom(average, max);
                result[i] = temp;
                money -= temp;
            }
        }
        // 如果还有余钱，则尝试加到小红包里，如果加不进去，则尝试下一个。
        while (money > 0) {
            for (i = 0; i < result.length; i++) {
                if (money > 0 && result[i] < max) {
                    result[i]++;
                    money--;
                }
            }
        }
        // 如果钱是负数了，还得从已生成的小红包中抽取回来
        while (money < 0) {
            for (i = 0; i < result.length; i++) {
                if (money < 0 && result[i] > min) {
                    result[i]--;
                    money++;
                }
            }
        }
        shuffleArray(result);
        return result;
    }

    /**
     * @param money   红包总额
     * @param total   红包个数
     * @param max     每个小红包的最大额
     * @param min     每个小红包的最小额
     * @param presets 预置红包个数及大小
     * @return 存放生成的每个小红包的值的数组
     */
    public static List<Long> presets(long money, int total, long max, long min, List<Long> presets) {
        //int presetLen = null == presets ? 0 : presets.size();
        List<Long> result = new ArrayList<>();

        long average = money / total + 1;

        //预置部分红包大小
        int i = 0;
        //
        //这样的随机数的概率实际改变了，产生大数的可能性要比产生小数的概率要小。
        //这样就实现了大部分红包的值在平均数附近。大红包和小红包比较少。
        //long range1 = sqr(average - min);
        //long range2 = sqr(max - average);

        for (; i < total; i++) {
            //因为小红包的数量通常是要比大红包的数量要多的，因为这里的概率要调换过来。
            //当随机数>平均值，则产生小红包
            //当随机数<平均值，则产生大红包
            if (nextLong(min, max) > average) {
                // 在平均线上减钱
                long temp = min + xRandom(min, average);
                result.add(temp);
                money -= temp;
            } else {
                // 在平均线上加钱
                long temp = max - xRandom(average, max);
                result.add(temp);
                money -= temp;
            }
        }
        // 如果还有余钱，则尝试加到小红包里，如果加不进去，则尝试下一个。
        while (money > 0) {
            for (i = 0; i < total; i++) {
                if (money > 0 && result.get(i) < max) {
                    result.set(i, result.get(i) + 1);
                    money--;
                }
            }
        }
        // 如果钱是负数了，还得从已生成的小红包中抽取回来
        while (money < 0) {
            for (i = 0; i < total; i++) {
                if (money < 0 && result.get(i) > min) {
                    result.set(i, result.get(i) - 1);
                    money++;
                }
            }
        }

        if (null != presets) {
            result.addAll(presets);
        }
        Collections.shuffle(result);
        return result;
    }

    private static long sqrt(long n) {
        // 改进为查表？
        return (long) Math.sqrt(n);
    }

    private static long sqr(long n) {
        // 查表快，还是直接算快？
        return n * n;
    }

    private static long nextLong(long n) {
        return JVMRandom.nextLong(n);
    }

    private static long nextLong(long min, long max) {
        return JVMRandom.nextLong((int) (max - min + 1)) + min;
    }

    public static void shuffleArray(long[] a) {
        int n = a.length;
        Random random = new Random();
        random.nextInt();
        for (int i = 0; i < n; i++) {
            int change = i + random.nextInt(n - i);
            swap(a, i, change);
        }
    }

    private static void swap(long[] a, int i, int change) {
        long helper = a[i];
        a[i] = a[change];
        a[change] = helper;
    }
}
